﻿/*********************************************************************************************
Main window based on application ExpressProfiler found in  Codeplex at
http://expressprofiler.codeplex.com


*********************************************************************************************/
// sample application for demonstrating Sql Server Profiling
// writen by Locky, 2009.
// Modified by Dave999

// Todo: 
// drag drop / finish control wire-ups.
// - logins
// SP
// Errors
// service broker.
// Any database 
// Update documentation.s
//
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.Threading;
using System.Windows.Forms;
using System.IO;
using System.Reflection;

namespace SqlServer.Profiler
{
	public partial class ProfilerForm : Form
	{
		public ProfilerForm(String connectionString, String database)
		{
			ConnectionString = connectionString;
			this.databaseName = database;

			InitializeComponent();
			Text = "DbViewSharp Sql Profiler";

			InitialiseProfilerConfiguration(ConnectionString);
			InitialiseSettings();
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////
		// Initialisation and startup
		///////////////////////////////////////////////////////////////////////////////////////////////////////

		private void MainForm_Load(object sender, EventArgs e)
		{
			// Load form with the default configuration
			ConfigureDefaultTrace(ConnectionString);
			
			// configure the profiler window to autoscroll
			this.ProfileWindow.SetScrollTraceWindow(tbScroll.Checked);
			// wire up the event handler
			this.ProfileWindow.ProfileStateChanged += new SqlProfileWindow.ProfileStateChangedEventHandler(UpdateButtons);

			// Always start the profiler
			StartProfiling();
		}

		// not sure I think once only
		private void InitialiseProfilerConfiguration(String connectionString)
		{
			if (this.sqlTraceConfiguration == null)
			{
				this.sqlTraceConfiguration = new ProfilerConfiguration();
				this.sqlTraceConfiguration.Initialise(connectionString);
			}
		}

		private void InitialiseSettings()
		{
			// Upgrade previous version
			bool upgradeHappened = SqlServerDriver.ProfilerSettings.CheckAndUpgrade();

			// if the upgrade happened there is only the default configuration stored.
			// need to add the preset slots
			String[] held = SqlServerDriver.ProfilerSettings.Settings;
			if (held.GetLength(0) > 0)
				this.configuration = held[0];

			// If none then set-up some.
			if (String.IsNullOrEmpty(this.configuration) || upgradeHappened)
			{
				//
				// This only happens once.
				//
				ConfigureDefault(this.sqlTraceConfiguration, false); // first in Settings list. Hopefully ignored in an upgraded situation.
				ConfigurePreset(1, this.sqlTraceConfiguration, false);
				ConfigurePreset(2, this.sqlTraceConfiguration, false);
				ConfigurePreset(3, this.sqlTraceConfiguration, false);
				ConfigurePreset(4, this.sqlTraceConfiguration, false);
			}
			// reget
			held = SqlServerDriver.ProfilerSettings.Settings;
			// define objects require for the call
			String key;
			String name;
			ProfilerEventsConfiguration[] events;
			ProfilerColumnConfiguration[] columns;
			comboConfigList.Items.Clear();
			foreach (String h in held)
			{
				key = ProfilerConfiguration.LoadXml(h, out name, out events, out columns);
				comboConfigList.Items.Add(name);
			}
			comboConfigList.SelectedIndex = 0;
		}

		private void ConfigureDefaultTrace(String connectionString)
		{
			// Future: Check static map and lookup the last config used using the connection string.
			this.configKey = SqlServerDriver.SqlServerSettingsMgr.SQLSERVER_SETTING_PROFILER_LASTUSED;
			this.configuration = SqlServerDriver.ProfilerSettings.Get(this.configKey);

			ConfigureCurrentTrace(this.configuration); return;
		}

		//
		// Upgrade of the one above.
		// Given Config Xml sets all the local members.
		// Won't set the dropdown though
		// 
		private void ConfigureCurrentTrace(String configXml)
		{
			this.configuration = configXml;
			// clear what is there already

			// load 
			ProfilerEventsConfiguration[] events;
			ProfilerColumnConfiguration[] columns;
			this.configKey = ProfilerConfiguration.LoadXml(this.configuration, out this.configName, out events, out columns);

			// Add filter on the database if it is sent in (normally yes)
			AddDataBaseFilterToLast(this.databaseName, columns);
		}

		private void AddDataBaseFilterToLast(String database, ProfilerColumnConfiguration[] reportColumns)
		{
			if (String.IsNullOrEmpty(database)) return; // nothing to do
			// search for the database column
			foreach (ProfilerColumnConfiguration column in reportColumns)
			{
				if (column.Id == ExpressProfiler.ProfilerEventColumns.DatabaseName)
				{
					// add the filter only if no other one is set
					// unless this holds the old one.
					if (String.IsNullOrEmpty(column.Filter))
					{
						ProfileFilter f = new ProfileFilter(column.Id, ProfileFilter.Op.eq, database);
						column.Filter = f.ToString();
					}
				}
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////
		// Form Events
		///////////////////////////////////////////////////////////////////////////////////////////////////////

		private void ToolBarStart_Click(object sender, EventArgs e) { ProfileWindow.StartProfiling(); }
		private void ToolBarStop_Click(object sender, EventArgs e) { ProfileWindow.StopProfiling(); }
		private void ToolBarPause_Click(object sender, EventArgs e) { ProfileWindow.PauseProfiling(); }
		private void ToolBarScroll_Click(object sender, EventArgs e) { ProfileWindow.SetScrollTraceWindow(tbScroll.Checked); }
		private void ToolBarClear_Click(object sender, EventArgs e) { ProfileWindow.ClearTrace(); }
		private void ToolBarConfigure_Click(object sender, EventArgs e)
		{
			ToolStripButton btn = sender as ToolStripButton;
			ProfileConfigurationForm.Tab initialTab = (btn == tbConfigureEvents) ? ProfileConfigurationForm.Tab.events :
													(btn == tbConfigureColumns) ? ProfileConfigurationForm.Tab.columns :
													(btn == tbConfigureFilter) ? ProfileConfigurationForm.Tab.filters : ProfileConfigurationForm.Tab.columns;
			OnConfigure(initialTab);
		}
		private void ToolBarReset_Click(object sender, EventArgs e) { OnReset(); }
		private void ToolBarLog_Click(object sender, EventArgs e) { OnToggleFileTrace();; }

		private void ComboConfigList_DropDownClosed(object sender, EventArgs e) { OnChangeConfig(false); }
		private void StatusStrip1_ItemClicked(object sender, ToolStripItemClickedEventArgs e) { OnClickStatusBar(e.ClickedItem); }

		private void OnClickStatusBar(ToolStripItem toolStripItem)
		{
			if (toolStripItem.Name.ToUpper() == "LOGFILE" && !String.IsNullOrEmpty(toolStripItem.Text))
		    {
		        Clipboard.SetText(toolStripItem.Text);
				System.Drawing.Color hold = toolStripItem.BackColor;
				toolStripItem.BackColor = System.Drawing.Color.Red;
				toolStripItem.Owner.Refresh();
				Thread.Sleep(50);
				// put everything back to original state.
				toolStripItem.BackColor = hold;
		    }
		}

		//private void OnClickStatusBar(String panelName)
		//{
		//    if (panelName.ToUpper() == "LOGFILE")
		//    {
		//        Clipboard.SetText(
		//    }
		////}

		private void OnChangeConfig(bool reset)
		{
			String selectedName = comboConfigList.SelectedItem.ToString();
			if (!reset) // not resetting (normal case ie selection from drop down
				if (selectedName == this.configName)
				{
					ProfileWindow.Focus();	// removes a big colour block from dropdown
					return;					// nothing to do so don't gratuitously clear the trace!
				}

			String[] held = SqlServerDriver.ProfilerSettings.Settings;
			bool logging = !(String.IsNullOrEmpty(ProfileWindow.LogFile));

			ProfileWindow.StopProfiling();

			// define objects require for the call
			String key;
			String name;
			ProfilerEventsConfiguration[] events;
			ProfilerColumnConfiguration[] columns;
			foreach (String h in held)
			{
				key = ProfilerConfiguration.LoadXml(h, out name, out events, out columns);
				if (name == selectedName)
				{
					this.configuration = h;
					this.configKey = key;
					this.configName = name;
					break;
				}
			}
			if (logging)
				SetLogFile(MakeLogFileName());

			StartProfiling();
			ProfileWindow.Focus();
		}

		private void OnConfigure(ProfileConfigurationForm.Tab initialTab)
		{
			String holdName;
			ProfilerEventsConfiguration[] e1;
			ProfilerColumnConfiguration[] c1;
			ProfilerConfiguration.LoadXml(this.configuration, out holdName, out e1, out c1);

			using (ProfileConfigurationForm dlg = new ProfileConfigurationForm(this.configName, e1, c1, initialTab))
			{
				if (dlg.ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
				{
					// get name in case it was changed
					this.configName = dlg.ConfigName;
					// Make an Xml document from the config dialog data
					this.configuration = ProfilerConfiguration.ToXml(this.configKey, this.configName, dlg.SelectedEvents, dlg.SelectedColumns);
					// update settings right away
					SqlServerDriver.ProfilerSettings.Update(this.configKey, this.configuration);
					
					//
					bool logging = !(String.IsNullOrEmpty(ProfileWindow.LogFile));
					ProfileWindow.StopProfiling();
					StartProfiling();
					if (logging)
						SetLogFile(MakeLogFileName());
					
					// if the name changed update the combo display.
					if (this.configName != holdName)
						UpdateCombo(holdName, this.configName);
				}
			}
		}

		// Use the information in the configuration string to start a new profile run
		private void StartProfiling()
		{
			ProfilerEventsConfiguration[] events;
			ProfilerColumnConfiguration[] columns;
			// Get configuration from the Xml Config
			this.configKey = ProfilerConfiguration.LoadXml(this.configuration, out configName, out events, out columns);
			// Automatically add a filter to restrict the profile to the current database
			// (but only if the database column is selected).
			// Users can disable this.
			AddDataBaseFilterToLast(this.databaseName, columns);

			this.ProfileWindow.SetConfiguration(ConnectionString, columns, events);
			this.ProfileWindow.StartProfiling();

			// display the filters applied 
			UpdateFilterPanel(columns);
			// set start, stop, pause button state.
			UpdateButtons(this, new ProfileStateChangeEventArgs(ProfileStateChangeEventArgs.ProfileState.Start));
		}

		private void UpdateCombo(string oldName, string newName)
		{
			// hold current index;
			int index = comboConfigList.SelectedIndex;
			comboConfigList.Items.Insert(index, newName);
			comboConfigList.Items.Remove(oldName);
			comboConfigList.SelectedIndex = index;
		}

		private void OnReset()
		{
			// 1. determine which config is active
			String resetKey = this.configKey;

			// 2. Structure message
			String message = "Confirm you wish to reset the configuration to its original state";

			if (MessageBox.Show(message, "Reset Configuration", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes)
			{
				// resetting the default
				if (resetKey == SqlServerDriver.SqlServerSettingsMgr.SQLSERVER_SETTING_PROFILER_LASTUSED)
					ConfigureDefault(this.sqlTraceConfiguration, true);
				// resetting a preset 
				else
				{
					String holdName = this.configName;
					// assume key is Preset1 etc
					// remove the text to leave the number
					String temp = resetKey.Replace(SqlServerDriver.SqlServerSettingsMgr.SQLSERVER_SETTING_PROFILER_PRESET_ROOT, "").Trim();

					int presetVal = System.Convert.ToInt32(temp);
					ConfigurePreset(presetVal, this.sqlTraceConfiguration, true);

					// This may not be correct in a later refactor.
					String name = String.Format("Preset {0}", presetVal);
					// replace the old name with the new name in the combo.
					UpdateCombo(holdName, name);
				}
				// force a restart
				OnChangeConfig(true);  // force a restart
			}
		}

		private void UpdateFilterPanel(ProfilerColumnConfiguration[] reportColumns)
		{
			String filterString = ProfilerConfiguration.FilterToString(reportColumns);
			if (filterString.Length > 0)
				filterString = filterString.Replace("&", "&&").Insert(0, "Filter: ");
			statusStrip1.Items[0].Text = filterString;
		}

		public void UpdateButtons(object sender, ProfileStateChangeEventArgs e)
		{
			// TODO: access profile state at arms length.
			tbStart.Enabled = (e.State == ProfileStateChangeEventArgs.ProfileState.Stop ||
							   e.State == ProfileStateChangeEventArgs.ProfileState.Pause);
			tbStop.Enabled = (e.State == ProfileStateChangeEventArgs.ProfileState.Pause ||
							   e.State == ProfileStateChangeEventArgs.ProfileState.Start);
			tbPause.Enabled = (e.State == ProfileStateChangeEventArgs.ProfileState.Start);
			//tbPause.Enabled = this.m_ProfilingState == ProfilingStateEnum.psProfiling;
		}

		private void OnToggleFileTrace()
		{
			ToolStripButton tb = tbLog;
			if (tb.Checked)
			{
				tb.Checked = false;
				SetLogFile("");
			}
			else
			{
				tb.Checked = true;
				SetLogFile(MakeLogFileName());
			}
		}

		void SetLogFile(String fileName)
		{
			ProfileWindow.LogFile = fileName;
			statusStrip1.Items[1].Text = fileName;
		}

		private string MakeLogFileName()
		{
			//
			// Special knowledge from the main application
			// about where to locate files. Drivers shouldn't really know this.
			// Code hacked from Program.cs.
			Assembly assem = Assembly.GetExecutingAssembly();
			String exeFile = assem.Location; // exe name
			String exePath = Path.GetDirectoryName(exeFile);  // path element
			String appFolder = "DbView"; // selected for legacy naming reasons.
			String dataPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), appFolder);
			// Portable version does not have DbView folder
			if (!Directory.Exists(dataPath))
				dataPath = exePath;
			
			// use time and date
			String fileName = "Profile_"+DateTime.Now.ToString("yyyyMMdd") + "_" + DateTime.Now.ToString("HHmmss")+".LOG";
			return Path.Combine(dataPath, fileName);
		}
		///////////////////////////////////////////////////////////////////////////////////////////////////////
		// members
		///////////////////////////////////////////////////////////////////////////////////////////////////////

		// supplied by the caller
		string databaseName = "";
		String ConnectionString { get; set; }

		// DM: Encapsulate the events and columns for tracing
		ProfilerConfiguration sqlTraceConfiguration = null;

		// profiler key (Hidden from user: __Lastused, Preset1, Preset2 etc.
		String configKey = SqlServerDriver.SqlServerSettingsMgr.SQLSERVER_SETTING_PROFILER_LASTUSED;
		// Name that appears in the drop down (empty for the default)
		String configName = "";
		// the whole Xml configuration
		String configuration = "";

		/////////////////////////////////////////////////////////////////////////////////////////////////
		// Hard Coded Trace Configurations from here
		///////////////////////////////////////////////////////////////////////////////////////////////////////

		static void ConfigureDefault(ProfilerConfiguration sqlTraceConfiguration, bool forceReset)
		{
			String key;
			String name;
			String configXml;
			List<ProfilerColumnConfiguration> reportColumns = new List<ProfilerColumnConfiguration>();
			List<ProfilerEventsConfiguration> eventList = new List<ProfilerEventsConfiguration>();

			// Key and Name
			key = SqlServerDriver.SqlServerSettingsMgr.SQLSERVER_SETTING_PROFILER_LASTUSED;
			name = "";
			configXml = SqlServerDriver.ProfilerSettings.Get(key);
			if (forceReset || String.IsNullOrEmpty(configXml))
			{
				// events
				eventList.Add(sqlTraceConfiguration.GetEventConfigById(ExpressProfiler.ProfilerEvents.TSQL.SQLBatchCompleted));
				// columns
				// Event Class fixed ie not configurable. Used by the UI
				reportColumns.Add(sqlTraceConfiguration.GetColumnConfigById(ExpressProfiler.ProfilerEventColumns.EventClass));
				// Configurable
				reportColumns.Add(sqlTraceConfiguration.GetColumnConfigById(ExpressProfiler.ProfilerEventColumns.DatabaseName));
				reportColumns.Add(sqlTraceConfiguration.GetColumnConfigById(ExpressProfiler.ProfilerEventColumns.TextData));
				reportColumns.Add(sqlTraceConfiguration.GetColumnConfigById(ExpressProfiler.ProfilerEventColumns.LoginName));
				reportColumns.Add(sqlTraceConfiguration.GetColumnConfigById(ExpressProfiler.ProfilerEventColumns.CPU));
				reportColumns.Add(sqlTraceConfiguration.GetColumnConfigById(ExpressProfiler.ProfilerEventColumns.Reads));
				reportColumns.Add(sqlTraceConfiguration.GetColumnConfigById(ExpressProfiler.ProfilerEventColumns.Duration));
				reportColumns.Add(sqlTraceConfiguration.GetColumnConfigById(ExpressProfiler.ProfilerEventColumns.SPID));
				// filters.

				// make it
				configXml = ProfilerConfiguration.ToXml(key, name, eventList.ToArray(), reportColumns.ToArray());
				SqlServerDriver.ProfilerSettings.Update(key, configXml);
			}
		}

		static void ConfigurePreset(int n, ProfilerConfiguration sqlTraceConfiguration, bool forceReset)
		{
			String key;
			String name;
			String configXml;
			List<ProfilerColumnConfiguration> reportColumns = new List<ProfilerColumnConfiguration>();
			List<ProfilerEventsConfiguration> eventList = new List<ProfilerEventsConfiguration>();

			// Key and Name
			key = String.Format("{0}{1}", SqlServerDriver.SqlServerSettingsMgr.SQLSERVER_SETTING_PROFILER_PRESET_ROOT, n);
			name = String.Format("Preset {0}", n);
			configXml = SqlServerDriver.ProfilerSettings.Get(key);
			if (forceReset || String.IsNullOrEmpty(configXml))
			{
				int [] eventId;
				int [] columnId;
				GetPresetConfig(n, out eventId, out columnId);
				foreach (int id in eventId)
					eventList.Add(sqlTraceConfiguration.GetEventConfigById(id));
				foreach (int id in columnId)
					reportColumns.Add(sqlTraceConfiguration.GetColumnConfigById(id));
				//// events
				//eventList.Add(sqlTraceConfiguration.GetEventConfigById(ExpressProfiler.ProfilerEvents.TSQL.SQLBatchCompleted));
				//// columns
				//// Event Class fixed ie not configurable. Used by the UI
				//reportColumns.Add(sqlTraceConfiguration.GetColumnConfigById(ExpressProfiler.ProfilerEventColumns.EventClass));
				//// Configurable
				//reportColumns.Add(sqlTraceConfiguration.GetColumnConfigById(ExpressProfiler.ProfilerEventColumns.DatabaseName));
				//reportColumns.Add(sqlTraceConfiguration.GetColumnConfigById(ExpressProfiler.ProfilerEventColumns.TextData));
				//reportColumns.Add(sqlTraceConfiguration.GetColumnConfigById(ExpressProfiler.ProfilerEventColumns.Duration));
				// filters.

				// make it
				configXml = ProfilerConfiguration.ToXml(key, name, eventList.ToArray(), reportColumns.ToArray());
				SqlServerDriver.ProfilerSettings.Update(key, configXml);
			}
		}
		// start to make useful presets
		private static void GetPresetConfig(int presetIndex, out int[] eventId, out int [] columnId)
		{
			switch (presetIndex)
			{
				case 2:    // debug errors
					eventId = new int[] {
						ExpressProfiler.ProfilerEvents.TSQL.SQLStmtStarting,
						ExpressProfiler.ProfilerEvents.StoredProcedures.SPStmtStarting,
						ExpressProfiler.ProfilerEvents.ErrorsAndWarnings.Exception
					};
					columnId = new int[] {
						ExpressProfiler.ProfilerEventColumns.DatabaseName,
						ExpressProfiler.ProfilerEventColumns.TextData,
						ExpressProfiler.ProfilerEventColumns.LoginName,
						ExpressProfiler.ProfilerEventColumns.Duration,
						ExpressProfiler.ProfilerEventColumns.Error,
						ExpressProfiler.ProfilerEventColumns.SPID
					};
					break;
				default:
					eventId = new int[] {
						ExpressProfiler.ProfilerEvents.TSQL.SQLBatchCompleted
					};
					columnId = new int[] {
						ExpressProfiler.ProfilerEventColumns.DatabaseName,
						ExpressProfiler.ProfilerEventColumns.TextData,
						ExpressProfiler.ProfilerEventColumns.LoginName,
						ExpressProfiler.ProfilerEventColumns.Duration
					};
					break;
			}
		}

	}
}
